package org.offsync.async

import java.nio.ByteBuffer
import java.nio.channels.AsynchronousFileChannel
import java.nio.channels.CompletionHandler
import java.nio.channels.FileLock
import java.nio.file.attribute.FileAttribute
import java.nio.file.OpenOption
import java.nio.file.Path
import java.util.concurrent.ExecutorService
import org.offsync.util.Disposable
import org.offsync.util.DisposeState
import org.offsync.util.Never
import org.offsync.util.Time

class AsyncFile(val underlying: AsynchronousFileChannel) extends Disposable {
  def force(metaData: Boolean) = underlying.force(metaData)

  def lock[A](attachment: A)(f: (() => FileLock, A) => Unit): Unit = {
    underlying.lock(attachment, new CompletionHandler[FileLock, A] {
      def completed(result: FileLock, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def lock(f: (() => FileLock) => Unit): Unit = {
    underlying.lock(null, new CompletionHandler[FileLock, Null] {
      def completed(result: FileLock, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }
  
  def lock[A](
      position: Long, size: Long, shared: Boolean, attachment: A)(
          f: (() => FileLock, A) => Unit): Unit = {
    underlying.lock(position, size, shared, attachment, new CompletionHandler[FileLock, A] {
      def completed(result: FileLock, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def lock(
      position: Long, size: Long, shared: Boolean)(
          f: (() => FileLock) => Unit): Unit = {
    underlying.lock(position, size, shared, null, new CompletionHandler[FileLock, Null] {
      def completed(result: FileLock, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def read[A](
      dst: ByteBuffer, position: Long, attachment: A)(
          f: (() => Int, A) => Unit): Unit = {
    underlying.read(dst, position, attachment, new CompletionHandler[Integer, A] {
      def completed(result: Integer, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }
  
  def read(
      dst: ByteBuffer, position: Long)(
          f: (() => Int) => Unit): Unit = {
    underlying.read(dst, position, null, new CompletionHandler[Integer, Null] {
      def completed(result: Integer, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }
  
  def size() = underlying.size()
  
  def truncate(size: Long) = underlying.truncate(size)
  
  def tryLock() = underlying.tryLock()
  
  def tryLock(position: Long, size: Long, shared: Boolean) = underlying.tryLock(position, size, shared)

  def write[A](src: ByteBuffer, position: Long, attachment: A)(f: (() => Integer, A) => Unit): Unit = {
    underlying.write(src, position, attachment, new CompletionHandler[Integer, A] {
      def completed(result: Integer, attachment: A): Unit = f({() => result}, attachment)
      def failed(t: Throwable, attachment: A): Unit = f({() => throw t}, attachment)
    })
  }

  def write(src: ByteBuffer, position: Long)(f: (() => Integer) => Unit): Unit = {
    underlying.write(src, position, null, new CompletionHandler[Integer, Null] {
      def completed(result: Integer, attachment: Null): Unit = f {() => result}
      def failed(t: Throwable, attachment: Null): Unit = f {() => throw t}
    })
  }

  def disposeState: DisposeState = if (underlying.isOpen) DisposeState.Live else DisposeState.Disposed
  
  def dispose(): Unit = underlying.close()
  
  def waitDisposed(deadline: Time = Never): Unit = {
    throw new UnsupportedOperationException()
  }
}

object AsyncFile {
  def open(
      path: Path,
      executor: ExecutorService,
      options: List[OpenOption],
      attrs: List[FileAttribute[AnyRef]]): AsyncFile = {
    import org.offsync.collection.conversion.s2j._
    val fileChannel = AsynchronousFileChannel.open(
        path, options.toSet, executor, attrs.toArray: _*)
    return new AsyncFile(fileChannel)
  }
  
  def open(
      path: Path,
      options: OpenOption*): AsyncFile = {
    return new AsyncFile(AsynchronousFileChannel.open(path, options: _*))
  }
}
